
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * Grid is a region which contains at most 4 cartesian systems
 *
 * TODO Default cartesian
 */
import { isObject, each, indexOf, retrieve3, keys } from 'zrender/lib/core/util.js';
import { getLayoutRect } from '../../util/layout.js';
import { createScaleByModel, ifAxisCrossZero, niceScaleExtent, estimateLabelUnionRect, getDataDimensionsOnAxis } from '../../coord/axisHelper.js';
import Cartesian2D, { cartesian2DDimensions } from './Cartesian2D.js';
import Axis2D from './Axis2D.js';
import { SINGLE_REFERRING } from '../../util/model.js';
import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper.js';
import { isIntervalOrLogScale } from '../../scale/helper.js';
import { alignScaleTicks } from '../axisAlignTicks.js';

var Grid =
/** @class */
function () {
	function Grid(gridModel, ecModel, api) {
		// FIXME:TS where used (different from registered type 'cartesian2d')?
		this.type = 'grid';
		this._coordsMap = {};
		this._coordsList = [];
		this._axesMap = {};
		this._axesList = [];
		this.axisPointerEnabled = true;
		this.dimensions = cartesian2DDimensions;

		this._initCartesian(gridModel, ecModel, api);

		this.model = gridModel;
	}

	Grid.prototype.getRect = function () {
		return this._rect;
	};

	Grid.prototype.update = function (ecModel, api) {
		var axesMap = this._axesMap;

		this._updateScale(ecModel, this.model);

		function updateAxisTicks(axes) {
			var alignTo; // Axis is added in order of axisIndex.

			var axesIndices = keys(axes);
			var len = axesIndices.length;

			if (!len) {
				return;
			}

			var axisNeedsAlign = []; // Process once and calculate the ticks for those don't use alignTicks.

			for (var i = len - 1; i >= 0; i--) {
				var idx = +axesIndices[i]; // Convert to number.

				var axis = axes[idx];
				var model = axis.model;
				var scale = axis.scale;

				if ( // Only value and log axis without interval support alignTicks.
					isIntervalOrLogScale(scale) && model.get('alignTicks') && model.get('interval') == null) {
					axisNeedsAlign.push(axis);
				} else {
					niceScaleExtent(scale, model);

					if (isIntervalOrLogScale(scale)) {
						// Can only align to interval or log axis.
						alignTo = axis;
					}
				}
			}

			 // All axes has set alignTicks. Pick the first one.
			// PENDING. Should we find the axis that both set interval, min, max and align to this one?

			if (axisNeedsAlign.length) {
				if (!alignTo) {
					alignTo = axisNeedsAlign.pop();
					niceScaleExtent(alignTo.scale, alignTo.model);
				}

				each(axisNeedsAlign, function (axis) {
					alignScaleTicks(axis.scale, axis.model, alignTo.scale);
				});
			}
		}

		updateAxisTicks(axesMap.x);
		updateAxisTicks(axesMap.y); // Key: axisDim_axisIndex, value: boolean, whether onZero target.

		var onZeroRecords = {};
		each(axesMap.x, function (xAxis) {
			fixAxisOnZero(axesMap, 'y', xAxis, onZeroRecords);
		});
		each(axesMap.y, function (yAxis) {
			fixAxisOnZero(axesMap, 'x', yAxis, onZeroRecords);
		}); // Resize again if containLabel is enabled
		// FIXME It may cause getting wrong grid size in data processing stage

		this.resize(this.model, api);
	};
	/**
   * Resize the grid
   */

	Grid.prototype.resize = function (gridModel, api, ignoreContainLabel) {
		var boxLayoutParams = gridModel.getBoxLayoutParams();
		var isContainLabel = !ignoreContainLabel && gridModel.get('containLabel');
		var gridRect = getLayoutRect(boxLayoutParams, {
			width: api.getWidth(),
			height: api.getHeight()
		});
		this._rect = gridRect;
		var axesList = this._axesList;
		adjustAxes(); // Minus label size

		if (isContainLabel) {
			each(axesList, function (axis) {
				if (!axis.model.get(['axisLabel', 'inside'])) {
					var labelUnionRect = estimateLabelUnionRect(axis);

					if (labelUnionRect) {
						var dim = axis.isHorizontal() ? 'height' : 'width';
						var margin = axis.model.get(['axisLabel', 'margin']);
						gridRect[dim] -= labelUnionRect[dim] + margin;

						if (axis.position === 'top') {
							gridRect.y += labelUnionRect.height + margin;
						} else if (axis.position === 'left') {
							gridRect.x += labelUnionRect.width + margin;
						}
					}
				}
			});
			adjustAxes();
		}

		each(this._coordsList, function (coord) {
			// Calculate affine matrix to accelerate the data to point transform.
			// If all the axes scales are time or value.
			coord.calcAffineTransform();
		});

		function adjustAxes() {
			each(axesList, function (axis) {
				var isHorizontal = axis.isHorizontal();
				var extent = isHorizontal ? [0, gridRect.width] : [0, gridRect.height];
				var idx = axis.inverse ? 1 : 0;
				axis.setExtent(extent[idx], extent[1 - idx]);
				updateAxisTransform(axis, isHorizontal ? gridRect.x : gridRect.y);
			});
		}
	};

	Grid.prototype.getAxis = function (dim, axisIndex) {
		var axesMapOnDim = this._axesMap[dim];

		if (axesMapOnDim != null) {
			return axesMapOnDim[axisIndex || 0];
		}
	};

	Grid.prototype.getAxes = function () {
		return this._axesList.slice();
	};

	Grid.prototype.getCartesian = function (xAxisIndex, yAxisIndex) {
		if (xAxisIndex != null && yAxisIndex != null) {
			var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
			return this._coordsMap[key];
		}

		if (isObject(xAxisIndex)) {
			yAxisIndex = xAxisIndex.yAxisIndex;
			xAxisIndex = xAxisIndex.xAxisIndex;
		}

		for (var i = 0, coordList = this._coordsList; i < coordList.length; i++) {
			if (coordList[i].getAxis('x').index === xAxisIndex || coordList[i].getAxis('y').index === yAxisIndex) {
				return coordList[i];
			}
		}
	};

	Grid.prototype.getCartesians = function () {
		return this._coordsList.slice();
	};
	/**
   * @implements
   */

	Grid.prototype.convertToPixel = function (ecModel, finder, value) {
		var target = this._findConvertTarget(finder);

		return target.cartesian ? target.cartesian.dataToPoint(value) : target.axis ? target.axis.toGlobalCoord(target.axis.dataToCoord(value)) : null;
	};
	/**
   * @implements
   */

	Grid.prototype.convertFromPixel = function (ecModel, finder, value) {
		var target = this._findConvertTarget(finder);

		return target.cartesian ? target.cartesian.pointToData(value) : target.axis ? target.axis.coordToData(target.axis.toLocalCoord(value)) : null;
	};

	Grid.prototype._findConvertTarget = function (finder) {
		var seriesModel = finder.seriesModel;
		var xAxisModel = finder.xAxisModel || seriesModel && seriesModel.getReferringComponents('xAxis', SINGLE_REFERRING).models[0];
		var yAxisModel = finder.yAxisModel || seriesModel && seriesModel.getReferringComponents('yAxis', SINGLE_REFERRING).models[0];
		var gridModel = finder.gridModel;
		var coordsList = this._coordsList;
		var cartesian;
		var axis;

		if (seriesModel) {
			cartesian = seriesModel.coordinateSystem;
			indexOf(coordsList, cartesian) < 0 && (cartesian = null);
		} else if (xAxisModel && yAxisModel) {
			cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
		} else if (xAxisModel) {
			axis = this.getAxis('x', xAxisModel.componentIndex);
		} else if (yAxisModel) {
			axis = this.getAxis('y', yAxisModel.componentIndex);
		} // Lowest priority.
		else if (gridModel) {
			var grid = gridModel.coordinateSystem;

			if (grid === this) {
				cartesian = this._coordsList[0];
			}
		}

		return {
			cartesian: cartesian,
			axis: axis
		};
	};
	/**
   * @implements
   */

	Grid.prototype.containPoint = function (point) {
		var coord = this._coordsList[0];

		if (coord) {
			return coord.containPoint(point);
		}
	};
	/**
   * Initialize cartesian coordinate systems
   */

	Grid.prototype._initCartesian = function (gridModel, ecModel, api) {
		var _this = this;

		var grid = this;
		var axisPositionUsed = {
			left: false,
			right: false,
			top: false,
			bottom: false
		};
		var axesMap = {
			x: {},
			y: {}
		};
		var axesCount = {
			x: 0,
			y: 0
		}; // Create axis

		ecModel.eachComponent('xAxis', createAxisCreator('x'), this);
		ecModel.eachComponent('yAxis', createAxisCreator('y'), this);

		if (!axesCount.x || !axesCount.y) {
			// Roll back when there no either x or y axis
			this._axesMap = {};
			this._axesList = [];
			return;
		}

		this._axesMap = axesMap; // Create cartesian2d

		each(axesMap.x, function (xAxis, xAxisIndex) {
			each(axesMap.y, function (yAxis, yAxisIndex) {
				var key = 'x' + xAxisIndex + 'y' + yAxisIndex;
				var cartesian = new Cartesian2D(key);
				cartesian.master = _this;
				cartesian.model = gridModel;
				_this._coordsMap[key] = cartesian;

				_this._coordsList.push(cartesian);

				cartesian.addAxis(xAxis);
				cartesian.addAxis(yAxis);
			});
		});

		function createAxisCreator(dimName) {
			return function (axisModel, idx) {
				if (!isAxisUsedInTheGrid(axisModel, gridModel)) {
					return;
				}

				var axisPosition = axisModel.get('position');

				if (dimName === 'x') {
					// Fix position
					if (axisPosition !== 'top' && axisPosition !== 'bottom') {
						// Default bottom of X
						axisPosition = axisPositionUsed.bottom ? 'top' : 'bottom';
					}
				} else {
					// Fix position
					if (axisPosition !== 'left' && axisPosition !== 'right') {
						// Default left of Y
						axisPosition = axisPositionUsed.left ? 'right' : 'left';
					}
				}

				axisPositionUsed[axisPosition] = true;
				var axis = new Axis2D(dimName, createScaleByModel(axisModel), [0, 0], axisModel.get('type'), axisPosition);
				var isCategory = axis.type === 'category';
				axis.onBand = isCategory && axisModel.get('boundaryGap');
				axis.inverse = axisModel.get('inverse'); // Inject axis into axisModel

				axisModel.axis = axis; // Inject axisModel into axis

				axis.model = axisModel; // Inject grid info axis

				axis.grid = grid; // Index of axis, can be used as key

				axis.index = idx;

				grid._axesList.push(axis);

				axesMap[dimName][idx] = axis;
				axesCount[dimName]++;
			};
		}
	};
	/**
   * Update cartesian properties from series.
   */

	Grid.prototype._updateScale = function (ecModel, gridModel) {
		// Reset scale
		each(this._axesList, function (axis) {
			axis.scale.setExtent(Infinity, -Infinity);

			if (axis.type === 'category') {
				var categorySortInfo = axis.model.get('categorySortInfo');
				axis.scale.setSortInfo(categorySortInfo);
			}
		});
		ecModel.eachSeries(function (seriesModel) {
			if (isCartesian2DSeries(seriesModel)) {
				var axesModelMap = findAxisModels(seriesModel);
				var xAxisModel = axesModelMap.xAxisModel;
				var yAxisModel = axesModelMap.yAxisModel;

				if (!isAxisUsedInTheGrid(xAxisModel, gridModel) || !isAxisUsedInTheGrid(yAxisModel, gridModel)) {
					return;
				}

				var cartesian = this.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
				var data = seriesModel.getData();
				var xAxis = cartesian.getAxis('x');
				var yAxis = cartesian.getAxis('y');
				unionExtent(data, xAxis);
				unionExtent(data, yAxis);
			}
		}, this);

		function unionExtent(data, axis) {
			each(getDataDimensionsOnAxis(data, axis.dim), function (dim) {
				axis.scale.unionExtentFromData(data, dim);
			});
		}
	};
	/**
   * @param dim 'x' or 'y' or 'auto' or null/undefined
   */

	Grid.prototype.getTooltipAxes = function (dim) {
		var baseAxes = [];
		var otherAxes = [];
		each(this.getCartesians(), function (cartesian) {
			var baseAxis = dim != null && dim !== 'auto' ? cartesian.getAxis(dim) : cartesian.getBaseAxis();
			var otherAxis = cartesian.getOtherAxis(baseAxis);
			indexOf(baseAxes, baseAxis) < 0 && baseAxes.push(baseAxis);
			indexOf(otherAxes, otherAxis) < 0 && otherAxes.push(otherAxis);
		});
		return {
			baseAxes: baseAxes,
			otherAxes: otherAxes
		};
	};

	Grid.create = function (ecModel, api) {
		var grids = [];
		ecModel.eachComponent('grid', function (gridModel, idx) {
			var grid = new Grid(gridModel, ecModel, api);
			grid.name = 'grid_' + idx; // dataSampling requires axis extent, so resize
			// should be performed in create stage.

			grid.resize(gridModel, api, true);
			gridModel.coordinateSystem = grid;
			grids.push(grid);
		}); // Inject the coordinateSystems into seriesModel

		ecModel.eachSeries(function (seriesModel) {
			if (!isCartesian2DSeries(seriesModel)) {
				return;
			}

			var axesModelMap = findAxisModels(seriesModel);
			var xAxisModel = axesModelMap.xAxisModel;
			var yAxisModel = axesModelMap.yAxisModel;
			var gridModel = xAxisModel.getCoordSysModel();

			if (process.env.NODE_ENV !== 'production') {
				if (!gridModel) {
					throw new Error('Grid "' + retrieve3(xAxisModel.get('gridIndex'), xAxisModel.get('gridId'), 0) + '" not found');
				}

				if (xAxisModel.getCoordSysModel() !== yAxisModel.getCoordSysModel()) {
					throw new Error('xAxis and yAxis must use the same grid');
				}
			}

			var grid = gridModel.coordinateSystem;
			seriesModel.coordinateSystem = grid.getCartesian(xAxisModel.componentIndex, yAxisModel.componentIndex);
		});
		return grids;
	}; // For deciding which dimensions to use when creating list data

	Grid.dimensions = cartesian2DDimensions;
	return Grid;
}();
/**
 * Check if the axis is used in the specified grid.
 */

function isAxisUsedInTheGrid(axisModel, gridModel) {
	return axisModel.getCoordSysModel() === gridModel;
}

function fixAxisOnZero(axesMap, otherAxisDim, axis, // Key: see `getOnZeroRecordKey`
	onZeroRecords) {
	axis.getAxesOnZeroOf = function () {
		// TODO: onZero of multiple axes.
		return otherAxisOnZeroOf ? [otherAxisOnZeroOf] : [];
	}; // onZero can not be enabled in these two situations:
	// 1. When any other axis is a category axis.
	// 2. When no axis is cross 0 point.

	var otherAxes = axesMap[otherAxisDim];
	var otherAxisOnZeroOf;
	var axisModel = axis.model;
	var onZero = axisModel.get(['axisLine', 'onZero']);
	var onZeroAxisIndex = axisModel.get(['axisLine', 'onZeroAxisIndex']);

	if (!onZero) {
		return;
	} // If target axis is specified.

	if (onZeroAxisIndex != null) {
		if (canOnZeroToAxis(otherAxes[onZeroAxisIndex])) {
			otherAxisOnZeroOf = otherAxes[onZeroAxisIndex];
		}
	} else {
		// Find the first available other axis.
		for (var idx in otherAxes) {
			if (otherAxes.hasOwnProperty(idx) && canOnZeroToAxis(otherAxes[idx]) // Consider that two Y axes on one value axis,
      // if both onZero, the two Y axes overlap.
      && !onZeroRecords[getOnZeroRecordKey(otherAxes[idx])]) {
				otherAxisOnZeroOf = otherAxes[idx];
				break;
			}
		}
	}

	if (otherAxisOnZeroOf) {
		onZeroRecords[getOnZeroRecordKey(otherAxisOnZeroOf)] = true;
	}

	function getOnZeroRecordKey(axis) {
		return axis.dim + '_' + axis.index;
	}
}

function canOnZeroToAxis(axis) {
	return axis && axis.type !== 'category' && axis.type !== 'time' && ifAxisCrossZero(axis);
}

function updateAxisTransform(axis, coordBase) {
	var axisExtent = axis.getExtent();
	var axisExtentSum = axisExtent[0] + axisExtent[1]; // Fast transform

	axis.toGlobalCoord = axis.dim === 'x' ? function (coord) {
		return coord + coordBase;
	} : function (coord) {
		return axisExtentSum - coord + coordBase;
	};
	axis.toLocalCoord = axis.dim === 'x' ? function (coord) {
		return coord - coordBase;
	} : function (coord) {
		return axisExtentSum - coord + coordBase;
	};
}

export default Grid;